Java 8 Stream常用方法学习

您所在的位置:网站首页 map stream 循环 Java 8 Stream常用方法学习

Java 8 Stream常用方法学习

2022-05-08 11:35| 来源: 网络整理| 查看: 265

文章目录 Stream基础概念1. 分类2. 常用方法2.1 forEach2.2 filter2.3 distinct2.4 Collectors - (Collector工具库)2.4.1 Collectors.toList()方法将Stream转化为List对象2.4.2 Collectors.toSet()方法将Stream转化为Set对象2.4.3 Collectors.toMap()方法将Stream转化为Map对象 2.4.4 统计2.4.5 分组2.4.6 joining(连接)2.4.7 reducing (规约)2.5 map、flatMap2.7 limit2.8 skip2.9 sorted2.10 统计结果收集器2.11 流的终止操作 3. 创建流的两种方式

Stream基础概念

Stream流是 Java8 API 新增的一个处理集合的关键抽象概念,是一个来自数据源的元素队列并支持聚合操作。以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

相关名词描述元素对象形成的一个队列。 Java中的Stream并不会存储元素,而是按需计算。数据源是流Stream的来源。 可以是集合、数组、I/O channel、 产生器generator 等聚合操作类似SQL语句一样的操作,比如filter, map, reduce, find, match, sorted等。内部迭代中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。Pipelining以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

Stream的特点:

Stream不是什么数据结构,它不会保存数据,只是将操作的数据结果保存到另一个对象中。Stream是惰性求值的(延迟执行),在中间处理的过程中,只对操作进行记录,并不会立即执行,只有等到执行终止操作的时候才会进行实际的计算,这时候中间操作才会执行。可以把Stream当成一个高级版本的Iterator来使用。(原始版本的Iterator,只能一个个的遍历操作) 1. 分类 Stream操作分类作用描述常用方法无状态中间操作, 该操作不受之前元素的影响unordered、filter、map、mapToInt、mapToLong、mapToDouble、flatMap、flatMapToInt、flatMapToLongg、flatMapToDouble、peek有状态中间操作,该操作只有拿到所有元素之后才能继续执行distinct、sorted、limit、skip非短路操作结束操作 , 必须处理所有元素才能得到最终结果forEach、forEachOrdered、toArray、collect、max、min、count、reduce短路操作结束操作 , 遇到某些符合条件的元素就可以得到最终结果anyMatch、allMatch、noneMatch、findFirsh、findAny 2. 常用方法

初始化一个String类型的List集合

List stringList = Arrays.asList("a","b", "", "", "c", "", "a", "d","e","a"); System.out.println("字符串集合:" + stringList);

运行结果:

字符串集合:[a, b, , , c, , a, d, e, a]

创建JavaBean对象User,初始化一个User列表

@Data class User{ String name; String description; } List userList = new ArrayList( Arrays.asList( new User("strive", "努力"), new User("fighter", "奋斗"), new User("lucky", "幸运"), new User("lucky", "幸运222") ) );

运行结果:

[User{name=‘strive’, description=‘努力’}, User{name=‘fighter’, description=‘奋斗’}, User{name=‘lucky’, description=‘幸运’}, User{name=‘lucky’, description=‘幸运222’}]

2.1 forEach

forEach方法用于迭代遍历每个数据

//迭代遍历输出 stringList.forEach(str ->{ System.out.print(str + " "); });

运行结果:

a b c a d e a

2.2 filter

使用 filter 按照设置的条件过滤元素,得到满足条件的元素。 在这里插入图片描述

//获取stringList中非空字符串的集合 List collect = stringList.stream().filter(str -> !str.isEmpty()) .collect(Collectors.toList()); System.out.println("非空字符串集合:" + collect);

运行结果:

非空字符串集合:[a, b, c, a, d, e, a]

count() 方法,用来统计数量

long emptyStrNum = stringList.stream().filter(str -> str.isEmpty()).count(); System.out.println("空字符串数量 = " + emptyStrNum);

运行结果:

空字符串数量 = 3

2.3 distinct

distinct() 方法用于去重 在这里插入图片描述

//获取stringList中非空字符串的集合 去重后 转化为list集合 List collect3 = stringList.stream().filter(str -> !str.isEmpty()).distinct() .collect(Collectors.toList()); System.out.println("去重后字符串集合:" + collect3); //提取出userList对象中的属性name并去重 List nameList = userList.stream().map(User::getName).distinct().collect(Collectors.toList()); System.out.println("nameList = " + nameList);

运行结果:

去重后字符串集合:[a, b, c, d, e]

nameList = [strive, fighter, lucky]

2.4 Collectors - (Collector工具库)

Collectors 类中实现了很多的规约操作(可用于返回列表或字符串)

最常用的是将流转换为 集合或聚合元素对象

2.4.1 Collectors.toList()方法将Stream转化为List对象 //查找非空、去重后通过 Collectors.toList() 转化为List列表 List strList = stringList.stream().filter(str -> !str.isEmpty()) .distinct().collect(Collectors.toList()); System.out.println("strList = " + strList);

运行结果:

strList = [a, b, c, d, e]

2.4.2 Collectors.toSet()方法将Stream转化为Set对象 //通过 Collectors.toSet() 方法转化为set列表 //set集合,不去重也输出相同的结果(set中不会有重复的元素) Set strSet = stringList.stream().filter(str -> !str.isEmpty()) .collect(Collectors.toSet()); System.out.println("strSet = " + strList);

运行结果:

strSet = [a, b, c, d, e]

2.4.3 Collectors.toMap()方法将Stream转化为Map对象 Collector toMap(Function keyMapper, Function valueMapper,BinaryOperator mergeFunction,Supplier mapSupplier)

参数1: keyMapper 用来生成key值的。

参数2: valueMapper 用来生成value值的。

参数3: mergeFunction 用在key值冲突的情况下使用(可省略),如果新元素产生的key在Map中已经出现过了,第三个参数就会定义解决的办法。

参数4:mapSupplier 默认返回的map类型为hashMap,可以按自己的需要自己返回不同的map实现。( 可省略 )

主要举个3个参数的案例来说明:

Collectors.toMap(keyMapper, valueMapper, mergeFunction)

该toMap()方法有三个参数.

比如 collect Collectors.toMap(User::getName, i -> i, (v1, v2) -> v1)

第一个参数 User::getName 表示选择User对象的的getName方法获取的值作为map的key值;

第二个参数 i -> i 表示选择将原来的对象作为map的value值(这里的i只是对遍历对象取的别名)

第三个参数 (v1, v2) -> v1,当出现key值相同时(也就是如果v1与v2的key值相同),选择前面的 也就是v1 作为那个key所对应的值,**就是出现相同key时,谁覆盖谁的问题 **

案例:

创建一个Javabean对象User

@Data class User{ String name; String description; }

A. 使用Collectors.toMap(keyMapper, valueMapper)两个参数来将List转化为Map

//初始化一个User的List列表 List list = new ArrayList( Arrays.asList( new User("strive", "努力"), new User("fighter", "奋斗"), new User("lucky", "幸运"), ) ); //Collectors.toMap()方法,将List转化为Map集合 //Map map = list.stream().collect(Collectors.toMap(item -> item.getName(), item -> item.getDescription())); //简写为下面这种方式 Map map = list.stream().collect(Collectors.toMap(User::getName, i -> i)); System.out.println(map);

运行结果:

{lucky=User{name=‘lucky’, description=‘幸运’}, strive=User{name=‘strive’, description=‘努力’}, fighter=User{name=‘fighter’, description=‘奋斗’}}

B. 如果出现key相同,但是没有设置第三个参数对其进行处理,就会报错 IllegalStateException:

//初始化一个User的List列表,里面有两个lucky List list = new ArrayList( Arrays.asList( new User("strive", "努力"), new User("fighter", "奋斗"), new User("lucky", "幸运"), new User("lucky", "幸运222") ) ); Map map = list.stream().collect(Collectors.toMap(User::getName, i -> i)); System.out.println(map);

运行结果:

报错:java.lang.IllegalStateException: Duplicate key User{name=‘lucky’, description=‘幸运’}

因为按照User的name转换有两个相同的key值lucky,没有选择处理方式报错:IllegalStateException

C. 设置第三个参数,解决出现相同key时,谁覆盖谁的问题

List list = new ArrayList( Arrays.asList( new User("strive", "努力"), new User("fighter", "奋斗"), new User("lucky", "幸运"), new User("lucky", "幸运222") ) ); //设置当key相同的时候,保留前面的 【如果为 (v1,v2)->v2 就是用新的覆盖旧的】 Map map = list.stream().collect(Collectors.toMap(User::getName, i -> i, (v1,v2) -> v1)); System.out.println(map);

运行结果:

{lucky=User{name=‘lucky’, description=‘幸运’}, strive=User{name=‘strive’, description=‘努力’}, fighter=User{name=‘fighter’, description=‘奋斗’}}

2.4.4 统计

count获取集合数量

List stringList = Arrays.asList("a","b", "", "", "c", "", "a", "d","e","a"); //获取集合StringList的元素数量 //Collectors.counting() stringList.stream().collect(Collectors.counting()); //简写为count() stringList.stream().count(); //等价于集合的size方法 (long) stringList.size();

运行结果:

三种结果最终的效果相同

10

10

10

求平均值:averagingInt、averagingLong、averagingDouble 求最大/最小值:maxBy、minBy 统计求和:summingInt、summingLong、summingDouble 统计所有(包括计数、求和、最小、最大、平均):summarizingInt、summarizingLong、summarizingDouble

List intList = Arrays.asList(2, 3, 10, 6, 8, 5, 2, 9); // maxBy 求最大值 -- 等价于max Optional maxBy = intList.stream().collect(Collectors.maxBy(Integer::compareTo)); // minBy 求最大值 -- 等价于min Optional minBy = intList.stream().collect(Collectors.minBy(Integer::compareTo)); // averagingInt 求平均值 Double averagingInt = intList.stream().collect(Collectors.averagingInt(i -> i)); // 求和summingInt(需要求和的参数) -- 等价于 mapToInt Integer summingInt = intList.stream().collect(Collectors.summingInt(i -> i)); //summarizingInt 统计数目、求和、最小值、平均值、最大值 IntSummaryStatistics summarizingInt = intList.stream().collect(Collectors.summarizingInt(i -> i)); System.out.println("maxBy = " + maxBy); System.out.println("minBy = " + minBy); System.out.println("averagingInt = " + averagingInt); System.out.println("summingInt = " + summingInt); System.out.println("summarizingInt = " + summarizingInt);

运行结果:

maxBy = Optional[10] minBy = Optional[2] averagingInt = 5.625 summingInt = 45 summarizingInt = IntSummaryStatistics{count=8, sum=45, min=2, average=5.625000, max=10}

2.4.5 分组

partitioningBy(分区):按照条件分为两个Map,一个是满足条件的Map和一个不满足条件的Map。 groupingBy(分组):类似于分区,但是是将集合按照条件分为多个Map,可以对进行分组之后的结果再分组

List intList = Arrays.asList(2, 3, 10, 6, 8, 5, 2, 9); //按 >5 分为两个区间 Map partitioningBy = intList.stream().collect(Collectors.partitioningBy(i -> i > 5)); //按 >5 分为两组 Map groupingBy = intList.stream().collect(Collectors.groupingBy(i -> i > 5)); //先按 >5 分为两组,然后再在前面分组满足条件的基础上对(满足条件的集合)再对 >8 进行分组 Map groupingBy2 = intList.stream().collect(Collectors.groupingBy(i -> i > 5, Collectors.groupingBy(i -> i > 8))); System.out.println("分区partitioningBy = " + partitioningBy); System.out.println("分组groupingBy = " + groupingBy); System.out.println("两次分组groupingBy2 = " + groupingBy2);

运行结果:

分区partitioningBy = {false=[2, 3, 5, 2], true=[10, 6, 8, 9]} 分组groupingBy = {false=[2, 3, 5, 2], true=[10, 6, 8, 9]} 两次分组groupingBy2 = {false={false=[2, 3, 5, 2]}, true={false=[6, 8], true=[10, 9]}}

2.4.6 joining(连接)

joining(条件):可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个新的字符串。

List stringList2 = Arrays.asList("a","b", " ", " ", "c", " ", "a", "d","e","a"); String joining = stringList2.stream().collect(Collectors.joining("-")); System.out.println("joining = " + joining);

运行结果:

joining = a-b- - -c- -a-d-e-a

2.4.7 reducing (规约) reducing(U identity, Function mapper, BinaryOperator op)

reducing:Collectors类提供的reducing方法,类似于stream本身的reduce方法,但是增加了对自定义归约的支持。 参数 BinaryOperator: 这是一个函数式接口,是给两个相同类型的量,返回一个跟这两个量相同类型的一个结果,伪表达式为 (T,T) -> T。默认给了两个实现 maxBy 和 minBy ,根据比较器来比较大小并分别返回最大值或者最小值。当然你也可以灵活定制。然后 reducing 就很好理解了,元素两两之间进行比较根据策略淘汰一个,随着轮次的进行元素个数就是 reduce 的。

List intList2 = Arrays.asList(1, 2, 3); Integer reducing = intList2.stream().collect(Collectors.reducing(100, i -> i, (v1, v2) -> (v1 + v2 - 1))); System.out.println("reducing = " + reducing);

运行结果:

reducing = 103

Stream提供的reduce方法也有类似的作用

// stream的reduce -- T reduce(T identity, BinaryOperator accumulator); //参1:(identity):求出结果之后再加该值 Optional reduce = Optional.ofNullable(intList2.stream().reduce(100, Integer::sum)); System.out.println("reduce = " + reduce.get());

运行结果:

reduce = 106

Optional reduce = Optional.ofNullable(intList2.stream().reduce(100, Integer::sum, (v1, v2) -> (v1 + v2 - 1))); System.out.println("reduce = " + reduce.get()); 2.5 map、flatMap

map方法用于 映射每个元素到对应的结果,该函数会被应用到每个元素上,并将其映射成一个新的元素。 在这里插入图片描述

flatMap方法用于 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。 在这里插入图片描述

//使用 map 输出stringList中元素对应的两倍并去重 List doubleStr = stringList.stream().map(str -> str += str).distinct() .collect(Collectors.toList()); System.out.print(doubleStr + " ");

运行结果:

[aa, bb, , cc, dd, ee]

//通过map获取userList列表中的User对象的name属性组成一个list列表 List nameList = list.stream().map(user -> user.getName()).distinct() .collect(Collectors.toList()); System.out.println(nameList);

运行结果:

[strive, fighter, lucky]

//flagMap转换为流后再转化为list列表输出 List s = stringList.stream().flatMap(s -> { //将每个元素按照分隔符,转换成一个stream String[] split = s.split(","); return Arrays.stream(split); }).collect(Collectors.toList()); System.out.println(s);

运行结果:

[a, b, , , c, , a, d, e, a]

2.6 peek

peek 方法(消费),类似于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,是没有返回值的。 在这里插入图片描述

//peek消费,去重输出 stringList.stream().peek(i -> i += i).distinct().forEach(i-> System.out.print(i + " "));

输出结果:

a b c d e

2.7 limit

limit(n) 方法用于获取指定数量 (n个) 的流 在这里插入图片描述

Random random = new Random(); // 通过 limit 输出3个随机数 random.ints().limit(3).forEach(r ->{ System.out.print(r + " "); });

运行结果:

1677393552 -608502510 2060923188

// 三个参数:random.ints(生成数量,最小值,最大值) //生成10个1-100之间的随机数,再通过limit取出5个输出 random.ints(10,1,100).limit(5).forEach(r ->{ System.out.print(r + " "); });

运行结果:

7 85 73 60 49

2.8 skip

skip(n) 方法用于跳过 (n个) 元素,配合 limit(n) 可实现分页 在这里插入图片描述

List numbers = Arrays.asList(0, 9 ,6 , 5, 6, 3, 2, 1, 12, 5, 8, 3, 9, 3); //过滤得到集合中>1的元素,然后去重,跳过前2个,然后取出5个 List collect = numbers.stream().filter(i -> i > 1) .distinct() .skip(2) .limit(5).collect(Collectors.toList()); System.out.println(collect);

运行结果:

[5, 3, 2, 12, 8]

2.9 sorted

sorted() 方法用于对流进行排序,这是自然排序,流中元素需实现Comparable接口。

sorted(Comparator com) : 带参数是定制排序,使用自定义的Comparator排序器进行排序

Random random = new Random(); //两个参数:random.ints(最小值,最大值) //随机生成0-1000的随机数,通过limit取8个,然后排序输出 random.ints(0,1000) .limit(8).sorted().forEach(i ->{ System.out.print(i + " "); });

运行结果:

86 192 232 552 560 776 928 929

2.10 统计结果收集器

比如:getCount、getMax、getMin、getSum、getAverage等等用于统计结果的收集器,主要用于int、double、long等基本类型上

//定义一个int类型的集合 List numbers = Arrays.asList(6, 2, 1, 2, 5, 8, 3, 9); //通过mapToInt转化 IntSummaryStatistics intStatus = numbers.stream().mapToInt((i) -> i).summaryStatistics(); System.out.println("列表中元素数量:" + intStatus.getCount()); System.out.println("列表中最大数 : " + intStatus.getMax()); System.out.println("列表中最小数 : " + intStatus.getMin()); System.out.println("所有数之和 : " + intStatus.getSum()); System.out.println("平均值 : " + intStatus.getAverage());

运行结果:

列表中元素数量:8 列表中最大数 : 9 列表中最小数 : 1 所有数之和 : 36 平均值 : 4.5

2.11 流的终止操作 方法名称描述count返回流中元素的总个数max返回流中元素最大值min返回流中元素最小值findFirst返回流中第一个元素findAny返回流中第一个元素(随机)allMatch接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回falsenoneMatch接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回falseanyMatch接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false List intList = Arrays.asList(2, 3, 10, 6, 8, 5, 2, 9); long count = intList.stream().count();// 等价于intList.size() Integer max = intList.stream().max(Integer::compareTo).get(); Integer min = intList.stream().min(Integer::compareTo).get(); Integer findFirst = intList.stream().findFirst().get(); Integer findAny = intList.stream().findAny().get(); boolean allMatch = intList.stream().allMatch(i -> i > 1); boolean noneMatch = intList.stream().noneMatch(i -> i > 1); boolean anyMatch = intList.stream().anyMatch(i -> i > 5);

运行结果:

count = 8 max = 10 min = 2 findFirst = 2 findAny = 2 allMatch = true noneMatch = false anyMatch = true

图片来自:https://ifeve.com/stream/

3. 创建流的两种方式 创建流的方式描述stream为集合创建串行流parallelStream为集合创建并行流

parallelStream里面的执行是异步的,并且使用的线程池是 ForkJoinPool.common,可以通过设置 -Djava.util.concurrent.ForkJoinPool.common.parallelism = N 来调整线程池的大小。可能提高你的多线程任务的速度。

List intList = Arrays.asList(1, 2, 3, 4, 5, 6); System.out.print("stream串行:"); intList.stream().forEach(System.out::print); System.out.print("\nparallelStream并行:"); intList.parallelStream().forEach(System.out::print);

三次运行结果:

第一次运行:

stream串行:123456 parallelStream并行:456321

第二次运行:

stream串行:123456 parallelStream并行:451623

第三次运行:

stream串行:123456 parallelStream并行:465321

可以发现,Stream每次运行都是相同的结果,且是顺序输出的,但是parallelStream每次运行结果可能不同,顺序也是错乱的。

打印执行线程信息,查看是否是并行的

List intList = Arrays.asList(1, 2, 3, 4, 5, 6); intList.parallelStream().forEach(i -> System.out.println("线程 = " + Thread.currentThread().getName() +" : 输出 = " + i));

运行结果:

线程 = main : 输出 = 4 线程 = ForkJoinPool.commonPool-worker-2 : 输出 = 1 线程 = ForkJoinPool.commonPool-worker-2 : 输出 = 3 线程 = ForkJoinPool.commonPool-worker-2 : 输出 = 5 线程 = main : 输出 = 6 线程 = ForkJoinPool.commonPool-worker-9 : 输出 = 2

所以 parallelStream 是利用多线程并行执行的,通过 parallelStream 可以很大程度简化我们使用并发操作。

使用 parallelStream 是平行处理的,所以顺序每次都不一定一致,如果想要顺序是按照原来Stream的数据一样顺序输出,可以通过 forEachOrdered 方法实现。

List intList = Arrays.asList(1, 2, 3, 4, 5, 6); System.out.println("parallelStream并行使用forEachOrdered顺序输出:"); intList.parallelStream().forEachOrdered(System.out::print); System.out.println(); intList.parallelStream().forEachOrdered(i -> System.out.println("线程 = " + Thread.currentThread().getName() +" : 输出 = " + i));

运行结果:

parallelStream并行使用forEachOrdered顺序输出:123456 线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 1 线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 2 线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 3 线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 4 线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 5 线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 6

所以调用 forEachOrdered 方法顺序执行的话,就不是多线程并行处理了,是一个线程进行处理。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3